package demo.common.dao;

import com.google.common.base.CaseFormat;
import demo.base.Join;
import demo.base.MapUtils;
import demo.base.StringUtils;
import demo.base.Template;
import demo.common.model.PagingCondition;
import demo.common.model.SqlCondition;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;


public class BaseProvider<T> {

    private static final Logger logger = LoggerFactory.getLogger(BaseProvider.class);

    public static final String limitSQL = " limit #{start}, #{length} ";

    private Class<T> entityClass;

    public BaseProvider() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
        logger.info("providerclass=" + this.getClass().getName() + ",entityclass=" + this.entityClass.getName());
    }

    public String updateByAnnotation(Class<? extends Annotation> group) {
        return EntitySQL.from(this.entityClass).updateByAnnotationSQL(group);
    }

    public String insert(Object entity) {
        return EntitySQL.from(this.entityClass).insertSQL();
    }

    public String get(Object id) {
        return EntitySQL.from(this.entityClass).getSQL();
    }

    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Table {
        public String tableName() default "";

        public String primaryKey() default "id";
    }

    @Target({ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface NoColumn {
    }

    public static class EntitySQL {

        private Class<?> entityClass;

        private EntitySQL(Class<?> entityClass) {
            this.entityClass = entityClass;
        }

        public static EntitySQL from(Class<?> obj) {
            return new EntitySQL(obj);
        }

        /**
         * 获取所有的父类,包括父类的父类
         */
        @SuppressWarnings("rawtypes")
        private List<Class> getSuperclasses(Class<?> class1) {
            List<Class> ret = new ArrayList<Class>();
            Class superClass = class1.getSuperclass();
            if (superClass != null) {
                ret.add(superClass);
                ret.addAll(getSuperclasses(superClass));
            }

            return ret;
        }

        /**
         * 获取类(包括父类,父类的父类)的Field
         */
        private List<Field> getAllDeclaredFields(Class class1) {

            List<Class> classes = new ArrayList<Class>();
            classes.add(class1);
            classes.addAll(getSuperclasses(class1));

            List<Field> ret = new ArrayList<Field>();
            for (Class class2 : classes) {
                Field[] fields = class2.getDeclaredFields();
                if (fields != null) {
                    for (Field field : fields) {
                        ret.add(field);
                    }
                }
            }
            return ret;

        }

        private Field findField(List<Field> fields, String name) {
            for (Field field : fields) {
                if (field.getName().equals(name)) {
                    return field;
                }
            }
            return null;
        }

        private List<String> getPersistPropertyNames() {

            try {

                List<Field> fields = getAllDeclaredFields(this.entityClass);

                List<String> propertyNames = new ArrayList<String>();
                BeanInfo beanInfo = Introspector.getBeanInfo(this.entityClass);
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                if (propertyDescriptors != null) {
                    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                        String name = propertyDescriptor.getName();
                        if ("class".equals(name)) {
                            continue;
                        }

                        if (propertyDescriptor.getReadMethod() != null &&
                                propertyDescriptor.getReadMethod().getAnnotation(NoColumn.class) != null) {
                            continue;
                        }

                        if (propertyDescriptor.getWriteMethod() != null &&
                                propertyDescriptor.getWriteMethod().getAnnotation(NoColumn.class) != null) {
                            continue;
                        }

                        Field field = findField(fields, name);
                        if (field != null && field.getAnnotation(NoColumn.class) != null) {
                            continue;
                        }

                        propertyNames.add(name);
                    }
                }
                return propertyNames;

            } catch (IntrospectionException e) {
                throw new RuntimeException("获取" + this.entityClass + "的反射信息失败", e);
            }
        }

        public String deleteSQL() {
            String tablename = this.tableName();
            String[] idname = this.idName();
            String condition = whereConditionByIdName(idname);
            return "delete from " + tablename + " where " + condition;
        }

        private String whereConditionByIdName(String[] idnames) {
            return whereConditionByIdNameWithParam(idnames, "");
        }

        private String whereConditionByIdNameWithParam(String[] idnames, String paramPrefix) {
            StringBuilder builder = new StringBuilder();
            boolean first = true;
            for (String idname : idnames) {
                if (!first) {
                    builder.append(" and ");
                }
                builder.append(upperCamelToLowerUnderscore(idname) + "=#{" + paramPrefix + idname + "}");
            }
            return builder.toString();
        }

        public String getSQL() {
            String tablename = this.tableName();
            String[] idname = this.idName();
            String condition = whereConditionByIdName(idname);
            String getsql = "select * from " + tablename + " where " + condition;
            logger.info("getsql=" + getsql);
            return getsql;
        }

        public String updateByAnnotationSQL(Class<? extends Annotation> group) {
            return updateByAnnotationSQL(group, "");
        }

        public String updateByAnnotationSQL(Class<? extends Annotation> group, String paramPrefix) {
            try {
                String tableName = this.tableName();
                List<String> propertyNames = getUpdatePartPropertyNames(group);

                String[] primaryKeys = idName();
                for (String primaryKey : primaryKeys) {
                    propertyNames.remove(primaryKey);
                }

                List<String> updatePropertySqls = new ArrayList<String>(propertyNames.size());
                for (String propertyName : propertyNames) {
                    updatePropertySqls.add(upperCamelToLowerUnderscore(propertyName) + "=#{" + paramPrefix + propertyName + "}");
                }

                String fields = StringUtils.seperate(updatePropertySqls, ",");

                String condition = whereConditionByIdNameWithParam(primaryKeys, paramPrefix);

                String sql = "update " + tableName +
                        " set " + fields +
                        " where " + condition;

                logger.info("update sql=" + sql);

                return sql;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private List<String> getUpdatePartPropertyNames(Class<? extends Annotation> group) throws IntrospectionException {
            List<Field> fields = getAllDeclaredFields(this.entityClass);

            String[] primaryKeys = idName();

            List<String> propertyNames = new ArrayList<String>();
            BeanInfo beanInfo = Introspector.getBeanInfo(this.entityClass);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            if (propertyDescriptors != null) {
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    String name = propertyDescriptor.getName();
                    if ("class".equals(name)) {
                        continue;
                    }

                    Field field = findField(fields, name);

                    if (ArrayUtils.contains(primaryKeys, name)
                            || (propertyDescriptor.getReadMethod() != null && propertyDescriptor.getReadMethod().getAnnotation(group) != null)
                            || (propertyDescriptor.getWriteMethod() != null && propertyDescriptor.getWriteMethod().getAnnotation(group) != null)
                            || (field != null && field.getAnnotation(group) != null)) {
                        propertyNames.add(name);
                    }

                }
            }
            return propertyNames;
        }

        public String insertSQL() {

            List<String> propertyNames = getPersistPropertyNames();
            List<String> tableColumeNames = upperCamelToLowerUnderscore(propertyNames);

            String tablename = this.tableName();

            String insertSql = "insert into " + tablename + "(" +
                    StringUtils.seperate(tableColumeNames, ",") +
                    ") values (" +
                    StringUtils.seperate(propertyNames, ",", "#{", "}") +
                    ")";

            logger.info("insert sql=" + insertSql);

            return insertSql;
        }

        public String replaceSQL() {

            List<String> propertyNames = getPersistPropertyNames();
            List<String> tableColumeNames = upperCamelToLowerUnderscore(propertyNames);

            String tablename = this.tableName();

            String insertSql = "replace into " + tablename + "(" +
                    StringUtils.seperate(tableColumeNames, ",") +
                    ") values (" +
                    StringUtils.seperate(propertyNames, ",", "#{", "}") +
                    ")";

            logger.info("replace sql=" + insertSql);

            return insertSql;
        }

        private List<String> upperCamelToLowerUnderscore(List<String> propertyNames) {
            List<String> result = new ArrayList<String>(propertyNames.size());
            for (String propertyName : propertyNames) {
                result.add(upperCamelToLowerUnderscore(propertyName));
            }
            return result;
        }

        private String upperCamelToLowerUnderscore(String propertyName) {
            return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, propertyName);
        }

        private String[] idName() {
            Table table = this.entityClass.getAnnotation(Table.class);
            if (table != null) {
                return table.primaryKey().split(",");
            }

            return new String[]{"id"};
        }

        private String tableName() {
            Table table = this.entityClass.getAnnotation(Table.class);
            if (table != null) {
                if (notEmpty(table.tableName())) {
                    return table.tableName();
                }
            }
            return this.entityClass.getSimpleName();
        }

        private boolean notEmpty(String s) {
            return s != null && !s.isEmpty();
        }


        public String buildCreateTableSQL() {
            StringBuilder fields = new StringBuilder();
            List<String> propertyNames = getPersistPropertyNames();
            if (propertyNames != null) {
                for (int i = 0; i < propertyNames.size(); i++) {
                    String propertyName = propertyNames.get(i);
                    fields.append("  " + upperCamelToLowerUnderscore(propertyName) + " " + getTableType(propertyName) + " not null,\n");
                }
            }

            String[] primaryKeys = idName();
            return new Template("CREATE TABLE {tableName} (\n{fields} PRIMARY KEY  ({primaryKeys}) ) ENGINE=InnoDB DEFAULT CHARSET=utf8", Template.TemplateType.SINGLE).format(
                    (Map) MapUtils.from(
                            "tableName", tableName(),
                            "fields", fields.toString(),
                            "primaryKeys", Join.join(primaryKeys, ",")
                    ));
        }

        private String getTableType(String propertyName) {
            try {
                Class<?> type = this.entityClass.getMethod("get" + upperCaseFirst(propertyName)).getReturnType();
                if (type.equals(String.class) || type.isEnum()) {
                    return "varchar(100)";
                } else if (type.equals(Date.class)) {
                    return "datetime";
                } else {
                    return "bigint(20)";
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private String upperCaseFirst(String line) {
            return Character.toUpperCase(line.charAt(0)) + line.substring(1);
        }

        public String pagingData(PagingCondition pagingCondition) {
            return "select * from " + tableName() + " " + whereSQL(pagingCondition) + " " + pagingCondition.orderSQL() + " " + limitSQL;
        }

        public String pagingTotal(PagingCondition pagingCondition) {
            return "select count(*) from " + tableName() + " " + whereSQL(pagingCondition);
        }

        private String whereSQL(PagingCondition pagingCondition) {
            List<String> wheres = pagingCondition.whereSQL();
            if (CollectionUtils.isNotEmpty(wheres)) {
                return " where " + Join.join(wheres, " and ");
            }
            return "";
        }

        public String find() {
            return "select * from " + tableName();
        }

        public String find(SqlCondition sqlCondition) {
            return "select * from " + tableName() + whereSQL(sqlCondition);
        }

        private String whereSQL(SqlCondition sqlCondition) {
            List<String> wheres = sqlCondition.whereSQL();
            if (CollectionUtils.isNotEmpty(wheres)) {
                return " where " + Join.join(wheres, " and ");
            }
            return "";
        }

    }

}

